home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Hyper Stacks 1994 May
/
Hyper Stacks (Pacific HiTech)(1994)[Mac].iso
/
Organization
/
TEX ƒ
/
zbrowser 0.5.c
< prev
Wrap
Text File
|
1988-09-04
|
38KB
|
1,351 lines
/* New "TEX" master XFCN "zbrowser(...)" - by ^z - 1988 Aug 22-Sep 4...
*
* copyright 1988 by Mark "^z" Zimmermann - all rights reserved.
* (Symantec/Think Technologies may also have copyrights on portions.)
* All standard legalistic disclaimers are included herewith by reference.
* If, for example, your use of this software causes a thermonuclear
* war and the destruction of civilization (as we know it), I am
* not responsible. Use the program at your own risk!!! I have
* never lost any data with it -- but there are no guarantees in life.
*
* This XFCN was developed using Think's Lightspeed C. It is available
* for nonexclusive licensing at a cost of 2% of retail price,
* per distributed copy of any for-sale software
* that uses it (as of Sept. 1988). My HyperCard stack using this XFCN,
* "TEX", is available via CompuServe, Arpanet, and other media.
*
***********************************************************************
* Individual users of TEX should send a $10 license fee to me at *
* the below address. The corporate license fee is $40 per copy in *
* simultaneous use. *
***********************************************************************
*
* In exchange for your license fee, you get:
* - information about extensions and enhancements to this program
* as they are come out;
* - free support and advice on its use;
* - copies of new versions for the cost of media and reproduction;
* - a nice warm feeling knowing that you are supporting further
* research into massive free-text dataspace tool-building.
*
* Be a part of the adventure! If you can't afford cash, take the time
* to write me a nice letter about how you're using TEX, or send a
* disk with your ideas/suggestions/modifications to the stack.
*
* All monies received are used to enhance this software and to pay for
* creation and distribution expenses. Comparable "commercial" free-text
* database products cost hundreds or thousands of dollars. You have
* something precious here -- take advantage of it, please! Build upon
* my work, extend it, and share your results with the community of
* researchers.
*
* I am very proud of this software. I have chosen to distribute it
* at nominal cost, so that it can be more widely used and can help
* more people. Please join me!
*
* For further information, and for license fee payments, write:
* Mark ^Zimmermann
* 9511 Gwyndale Drive
* Silver Spring, MD 20910
* USA
*
* Be sure to enclose a STAMPED, SELF-ADDRESSED ENVELOPE if you want
* to receive a timely reply!
*
* Electronic addresses:
* science@nems.arpa
* [75066,2044] CompuServe
* tel. 301-565-2166
*/
/* see the end of this file for detailed information on the various
* TEX functions and their calling conventions
*/
/* ------------------------header files to include-------------- */
#include <MacTypes.h>
#include <FileMgr.h>
#include <HyperXCmd.h>
/* ---------------declarations and definitions------------------- */
/* KEY_LENGTH is the number of letters we have in each index record;
* 28 is the value chosen for the past year as optimal...don't
* change it without good reason!
*/
#define KEY_LENGTH 28
/* SUBSPACE_QUANTUM is the distance in bytes that defines the proximity
* neighborhood of a word in the index ... it is used when defining a
* subset for pseudo-proximity searching.... SUBSPACE_QUANTUM * 8 is the
* effective compression factor for squeezing the text file down into
* an array of one-bit flags showing which regions of the dataspace are
* in the current working subspace.
*
* Thus, SUBSPACE_QUANTUM = 32, a nice choice, defines a chunkiness of 32
* characters in making comparisons for proximity determination purposes,
* and results in a compression factor of 256. Thus, in a typical 1 MB Mac
* with ~100kB free running HyperCard, that should allow subspace browsing
* of dataspaces up to ~25 MB....
*/
#define SUBSPACE_QUANTUM 32
/* structure of the records in the index key file:
* a fixed-length character string, padded out with blanks and
* containing the unique alphanumeric 'words' in the document
* file, changed to all-capital letters;
* a cumulative count of how many total occurrences of words, including
* the current one, have appeared up to this point in the sorted
* index.
*/
typedef struct
{
char kkey[KEY_LENGTH];
long ccount;
} KEY_RECORD;
/* some symbolic values... */
#define TRUE 1
#define FALSE 0
#define NULL 0
/* define these to allow use of global variables in my XFCNs; a
* Lightspeed C necessity, apparently...
*/
#define SetUpA4() asm { move.l a4,-(sp) \
move.l a0,a4 }
#define RestoreA4() asm { move.l (sp)+,a4 }
/* ---------------prototypes------------------- */
pascal void main (XCmdBlockPtr paramPtr);
void doContext (XCmdBlockPtr paramPtr);
void doEmptySubspace (XCmdBlockPtr paramPtr);
void doFillSubspace (XCmdBlockPtr paramPtr);
void doIndex (XCmdBlockPtr paramPtr);
void doLocate (XCmdBlockPtr paramPtr);
void doNewSubspace (XCmdBlockPtr paramPtr);
void doReleaseSubspace (XCmdBlockPtr paramPtr);
void doSetSubspaceBits (XCmdBlockPtr paramPtr);
void doText (XCmdBlockPtr paramPtr);
void returnErrorMsg (XCmdBlockPtr paramPtr, char *msg);
void getKeyRecord (KEY_RECORD *keyRecp, long keyRecNum, int keyFileRefNum);
long getTextPtr (long instanceNum, int ptrFileRefNum);
void getContextLine (long bytes, long textPtr, int refNum, char *ansp);
void buildIndexAnswer (char *ansp, int indexCountWidth, int indexKeyWidth,
long count, char key[]);
void buildSubIndexAnswer (char *ansp, int indexCountWidth,
int indexKeyWidth, long maxIndexSampleCount, long prevCcount,
long thisCcount, char key[], Handle subspaceHandle, int ptrFileRefNum);
int inSubspace (long textPtr, Handle subspaceHandle);
void setSSBit (long textPtr, int setOrClear, Handle subspaceHandle);
char *strcpy (char *s1, char *s2);
int strlen (char *s);
int zstrcmp (unsigned char *s1, unsigned char *s2);
long atol (char *s);
void ltoaR (char *ansp, long n, int maxDigits);
/* ------------------------main program-------------- */
/* main routine, to dispatch control to a function
* defined by the first letter of the first argument of the XFCN
*/
pascal void main (paramPtr)
XCmdBlockPtr paramPtr;
{
SetUpA4();
switch (**(paramPtr->params[0]))
{
case 'C':
doContext (paramPtr);
break;
case 'E':
doEmptySubspace (paramPtr);
break;
case 'F':
doFillSubspace (paramPtr);
break;
case 'I':
doIndex (paramPtr);
break;
case 'L':
doLocate (paramPtr);
break;
case 'N':
doNewSubspace (paramPtr);
break;
case 'R':
doReleaseSubspace (paramPtr);
break;
case 'S':
doSetSubspaceBits (paramPtr);
break;
case 'T':
doText (paramPtr);
break;
default:
returnErrorMsg (paramPtr,
"{Sorry, unrecognized command in TEX zbrowser XFCN call!}");
break;
}
RestoreA4();
return;
}
/* ---------------------major functional units-------------- */
/* function to create the context display...
*
* ("CONTEXT", instanceNum, contextLines, targetContextLine,
* contextLineLength, contextWordOffset, maxContextLinesSkipped,
* ptrFileRefNum, textFileRefNum, subspaceHandle)
* -- returns with contextLines of display followed by contextLines
* of instanceNum-textPtr pairs, with context instance instanceNum
* on line targetContextLine...
*/
void doContext (paramPtr)
XCmdBlockPtr paramPtr;
{
int contextLines, targetContextLine, contextLineLength,
contextWordOffset, ptrFileRefNum, textFileRefNum, line;
long instanceNum, maxContextLinesSkipped, textPtr, j, *tempNum;
Handle subspaceHandle, answer, tempStor;
char *ansp;
if (paramPtr->paramCount != 10)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong number of parameters in XFCN CONTEXT call!}");
return;
}
instanceNum = atol (*(paramPtr->params[1]));
contextLines = atol (*(paramPtr->params[2]));
targetContextLine = atol (*(paramPtr->params[3]));
contextLineLength = atol (*(paramPtr->params[4]));
contextWordOffset = atol (*(paramPtr->params[5]));
maxContextLinesSkipped = atol (*(paramPtr->params[6]));
ptrFileRefNum = atol (*(paramPtr->params[7]));
textFileRefNum = atol (*(paramPtr->params[8]));
subspaceHandle = (Handle) atol (*(paramPtr->params[9]));
if (instanceNum < 0 || contextLines < 1 || targetContextLine < 1 ||
targetContextLine > contextLines ||
contextLineLength < KEY_LENGTH + contextWordOffset ||
contextWordOffset < 0 || maxContextLinesSkipped < 1 ||
ptrFileRefNum == NULL || textFileRefNum == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, bad parameter in XFCN CONTEXT call!}");
return;
}
if ((answer = NewHandle (contextLines * (contextLineLength + 26) + 1))
== NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, out of memory error in XFCN CONTEXT call!}");
return;
}
/* tempStor is used to store values for instanceNums and textPtrs */
if ((tempStor = NewHandle (contextLines * 2 * sizeof(long))) == NULL)
{
DisposHandle (answer);
returnErrorMsg (paramPtr,
"{Sorry, secondary out-of-memory error in XFCN CONTEXT call!}");
return;
}
/* back up to the right starting instanceNum */
for (line = targetContextLine; line > 1; --line)
{
for (j = 0; j < maxContextLinesSkipped; ++j)
{
textPtr = getTextPtr (--instanceNum, ptrFileRefNum);
if (subspaceHandle == NULL || textPtr < 0 ||
inSubspace (textPtr, subspaceHandle))
break;
}
}
HLock (answer);
HLock (tempStor);
ansp = *answer;
tempNum = (long *) *tempStor;
/* generate the lines of the context display, saving numbers */
for (line = 0; line < contextLines; ++line)
{
for (j = 0; j < maxContextLinesSkipped; ++j, ++instanceNum)
{
textPtr = getTextPtr (instanceNum, ptrFileRefNum);
if (textPtr < 0)
break;
if (subspaceHandle == NULL ||
inSubspace (textPtr, subspaceHandle))
{
getContextLine (contextLineLength,
textPtr - contextWordOffset, textFileRefNum, ansp);
ansp += contextLineLength;
break;
}
}
if (j == maxContextLinesSkipped)
{
textPtr = -1;
--instanceNum;
for (j = 0; j < contextLineLength; ++j)
*ansp++ = '.';
}
tempNum[line] = instanceNum++;
tempNum[line + contextLines] = textPtr;
*ansp++ = '\r';
}
for (line = 0; line < contextLines; ++line)
{
ltoaR (ansp, tempNum[line], 12);
ansp += 12;
ltoaR (ansp, tempNum[line + contextLines], 12);
ansp += 12;
*ansp++ = '\r';
}
*ansp = '\0';
HUnlock (answer);
paramPtr->returnValue = answer;
HUnlock (tempStor);
DisposHandle (tempStor);
return;
}
/* function to empty out a subspace so that no words are in the valid
* region:
*
* ("EMPTYSUBSPACE", subspaceHandle)
* -- returns quietly with nothing if it successfully sets all bits
* in the subspace flag array to zero; beeps and gives an error msg
* if it fails somehow...
*/
void doEmptySubspace (paramPtr)
XCmdBlockPtr paramPtr;
{
long subspaceSize;
Handle subspaceHandle;
register char *cp, *endOfSubspace;
if (paramPtr->paramCount != 2)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong # of parameters in XFCN EMPTYSUBSPACE call!}");
return;
}
subspaceHandle = (Handle) atol (*(paramPtr->params[1]));
if (subspaceHandle == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, NULL subspaceHandle in XFCN EMPTYSUBSPACE call!}");
return;
}
subspaceSize = GetHandleSize (subspaceHandle);
endOfSubspace = *subspaceHandle + subspaceSize;
for (cp = *subspaceHandle; cp < endOfSubspace; ++cp)
*cp = 0x00;
return;
}
/* function to fill a subspace so that the entire dataspace is in the
* valid region:
*
* ("FILLSUBSPACE", subspaceHandle)
* -- returns quietly with nothing if it successfully sets all bits
* in the subspace flag array to one; beeps and gives an error msg
* if failure...
*/
void doFillSubspace (paramPtr)
XCmdBlockPtr paramPtr;
{
long subspaceSize;
Handle subspaceHandle;
register char *cp, *endOfSubspace;
if (paramPtr->paramCount != 2)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong # of parameters in XFCN FILLSUBSPACE call!}");
return;
}
subspaceHandle = (Handle) atol (*(paramPtr->params[1]));
if (subspaceHandle == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, NULL subspaceHandle in XFCN FILLSUBSPACE call!}");
return;
}
subspaceSize = GetHandleSize (subspaceHandle);
endOfSubspace = *subspaceHandle + subspaceSize;
for (cp = *subspaceHandle; cp < endOfSubspace; ++cp)
*cp = 0xFF;
return;
}
/* function to produce the index window display and associated info
*
* ("INDEX", wordNum, indexLines, maxIndexSampleCount, indexCountWidth,
* indexKeyWidth, keyFileRefNum, ptrFileRefNum, subspaceHandle)
* -- returns with indexLines of index window display, followed by
* indexLines of instanceNums. The index lines are:
* indexCountWidth columns of occurrence count info (right-justified),
* a blank column, and indexKeyWidth columns of keyWord (in all
* caps, left-justified). Demand that indexCountWidth be at least
* 5, to allow for subindex count display, and that indexKeyWidth
* be in the range 1 through KEY_LENGTH = 28 ...
*/
void doIndex (paramPtr)
XCmdBlockPtr paramPtr;
{
KEY_RECORD thisRec, prevRec;
register int i;
int indexLines, keyFileRefNum, indexCountWidth, ptrFileRefNum,
keyRecsFound, indexKeyWidth;
long wordNum, maxIndexSampleCount;
Handle subspaceHandle, answer;
char *ansp;
if (paramPtr->paramCount != 9)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong number of parameters in XFCN INDEX call!}");
return;
}
wordNum = atol (*(paramPtr->params[1]));
indexLines = atol (*(paramPtr->params[2]));
maxIndexSampleCount = atol (*(paramPtr->params[3]));
indexCountWidth = atol (*(paramPtr->params[4]));
indexKeyWidth = atol (*(paramPtr->params[5]));
keyFileRefNum = atol (*(paramPtr->params[6]));
ptrFileRefNum = atol (*(paramPtr->params[7]));
subspaceHandle = (Handle) atol (*(paramPtr->params[8]));
if (wordNum < 0 || indexLines < 1 || maxIndexSampleCount < 1 ||
indexCountWidth < 5 || indexKeyWidth < 1 ||
indexKeyWidth > KEY_LENGTH || keyFileRefNum == 0 ||
ptrFileRefNum == 0)
{
returnErrorMsg (paramPtr,
"{Sorry, bad parameter in XFCN INDEX call!}");
return;
}
if ((answer = NewHandle (indexLines *
(indexCountWidth + indexKeyWidth + 15) + 1)) == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, out of memory error in XFCN INDEX call!}");
return;
}
HLock (answer);
ansp = *answer;
getKeyRecord (&prevRec, wordNum - 1, keyFileRefNum);
for (i = 0; i < indexLines; ++i)
{
getKeyRecord (&thisRec, wordNum + i, keyFileRefNum);
if (thisRec.ccount == 0)
break;
if (subspaceHandle == NULL)
buildIndexAnswer (ansp, indexCountWidth, indexKeyWidth,
thisRec.ccount - prevRec.ccount, thisRec.kkey);
else
buildSubIndexAnswer (ansp, indexCountWidth, indexKeyWidth,
maxIndexSampleCount, prevRec.ccount, thisRec.ccount,
thisRec.kkey, subspaceHandle, ptrFileRefNum);
ansp += indexCountWidth + indexKeyWidth + 2;
prevRec.ccount = thisRec.ccount;
}
keyRecsFound = i;
for (i = keyRecsFound; i < indexLines; ++i)
*ansp++ = '\r';
for (i = 0; i < keyRecsFound; ++i)
{
getKeyRecord (&thisRec, wordNum + i - 1, keyFileRefNum);
ltoaR (ansp, thisRec.ccount, 12);
ansp += 12;
*ansp++ = '\r';
}
for (i = keyRecsFound; i < indexLines; ++i)
*ansp++ = '\r';
*ansp = '\0';
HUnlock (answer);
paramPtr->returnValue = answer;
return;
}
/* function to find a chosen string in the index key file (just do a
* binary search to locate it):
*
* ("LOCATE", targetString, keyFileRefNum)
* -- returns wordNum for the targetString if it is found in the
* key file; otherwise returns wordNum for the word alphabetically
* preceding targetString followed by "{targetString not found!}"
* on the second line of the answer...
*
*/
void doLocate (paramPtr)
XCmdBlockPtr paramPtr;
{
register int i, c;
int keyFileRefNum, diff;
char *cp;
register long mid;
long low, high, keyFileSize;
KEY_RECORD thisRec, targetRec;
Handle answer;
if (paramPtr->paramCount != 3)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong number of parameters in XFCN LOCATE call!}");
return;
}
keyFileRefNum = atol (*(paramPtr->params[2]));
if (keyFileRefNum == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, NULL keyFileRefNum error in XFCN LOCATE call!}");
return;
}
cp = *(paramPtr->params[1]);
for (i = 0; i < KEY_LENGTH; ++i)
{
c = *cp;
if (c == '\0')
{
targetRec.kkey[i] = ' ';
continue;
}
if (c >= 'a' && c <= 'z')
c = c - 'a' + 'A';
targetRec.kkey[i] = c;
++cp;
}
low = 0;
GetEOF (keyFileRefNum, &keyFileSize);
high = keyFileSize / sizeof (KEY_RECORD) - 1;
while (low <= high)
{
mid = (low + high) / 2;
getKeyRecord (&thisRec, mid, keyFileRefNum);
if (thisRec.ccount == 0)
{
returnErrorMsg (paramPtr,
"{Sorry, possible file I/O error in XFCN LOCATE call!}");
return;
}
diff = zstrcmp ((unsigned char *)targetRec.kkey,
(unsigned char *)thisRec.kkey);
if (diff < 0)
high = mid - 1;
else if (diff > 0)
low = mid + 1;
else
break;
}
if (diff < 0)
--mid;
if (mid < 0)
mid = 0;
if ((answer = NewHandle (64)) == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, out of memory error in XFCN LOCATE call!}");
return;
}
ltoaR (*answer, mid, 12);
*(*answer + 12) = '\0';
if (diff != 0)
strcpy (*answer + 12, "\r{target string not found!}");
paramPtr->returnValue = answer;
return;
}
/* function to create a new subspace:
*
* ("NEWSUBSPACE", textFileRefNum)
* -- returns subspaceHandle for a new subspace that it creates, big
* enough to do subspace browsing -- but does NOT initialize that
* subspace or check to see whether another subspace already
* exists. Beeps and gives error msg if it fails...
*/
void doNewSubspace (paramPtr)
XCmdBlockPtr paramPtr;
{
int textFileRefNum;
long textFileSize, subspaceSize;
Handle subspaceHandle, answer;
if (paramPtr->paramCount != 2)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong # of parameters in XFCN NEWSUBSPACE call!}");
return;
}
textFileRefNum = atol (*(paramPtr->params[1]));
if (textFileRefNum == NULL ||
GetEOF (textFileRefNum, &textFileSize) != noErr)
{
returnErrorMsg (paramPtr,
"{Sorry, file error in XFCN NEWSUBSPACE call!}");
return;
}
subspaceSize = 1 + textFileSize / (SUBSPACE_QUANTUM * 8);
if ((subspaceHandle = NewHandle (subspaceSize)) == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, not enough memory for subspace creation!}");
return;
}
if ((answer = NewHandle (16)) == NULL)
{
DisposHandle (subspaceHandle);
returnErrorMsg (paramPtr,
"{Sorry, out of memory error in XFCN NEWSUBSPACE call!}");
return;
}
ltoaR (*answer, (long)subspaceHandle, 12);
*(*answer + 12) = '\0';
paramPtr->returnValue = answer;
return;
}
/* routine to get rid of a subspace and release that memory:
*
* ("RELEASESUBSPACE", subspaceHandle)
* -- returns quietly with nothing if successful in releasing the
* subspaceHandle, or noisily with an error message if it fails...
*/
void doReleaseSubspace (paramPtr)
XCmdBlockPtr paramPtr;
{
Handle subspaceHandle;
if (paramPtr->paramCount != 2)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong # of params in XFCN RELEASESUBSPACE call!}");
return;
}
subspaceHandle = (Handle) atol (*(paramPtr->params[1]));
if (subspaceHandle == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, NULL subspaceHandle in XFCN RELEASESUBSPACE call!}");
return;
}
DisposHandle (subspaceHandle);
return;
}
/* function to turn on or off bits in a subspace according to their
* proximity to a given word's occurrences:
*
* ("SETSUBSPACEBITS", wordNum, neighborhoodSize, setOrClear,
* keyFileRefNum, ptrFileRefNum, subspaceHandle)
* -- returns quietly with nothing if it is successful in setting or
* clearing (depending on setOrClear's value, 0 or non-0) the
* bits in the subspace flag array in the neighborhood of the
* chosen word(s); gives an error msg if there was a problem.
* neighborhoodSize is in characters and is used to determine
* how many bits to set/clear on each side of the instances...
*/
void doSetSubspaceBits (paramPtr)
XCmdBlockPtr paramPtr;
{
long wordNum, neighborhoodSize, bitsToSet, maxTextPtr, instance,
tp0, tp, tpMax;
int setOrClear, keyFileRefNum, ptrFileRefNum;
Handle subspaceHandle;
KEY_RECORD prevRec, thisRec;
if (paramPtr->paramCount != 7)
{
returnErrorMsg (paramPtr,
"{Sorry, wrong # of parameters in XFCN SETSUBSPACE call!}");
return;
}
wordNum = atol (*(paramPtr->params[1]));
neighborhoodSize = atol (*(paramPtr->params[2]));
setOrClear = atol (*(paramPtr->params[3]));
keyFileRefNum = atol (*(paramPtr->params[4]));
ptrFileRefNum = atol (*(paramPtr->params[5]));
subspaceHandle = (Handle) atol (*(paramPtr->params[6]));
if (wordNum < 0 || neighborhoodSize < 1 ||
keyFileRefNum == NULL || ptrFileRefNum == NULL ||
subspaceHandle == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, bad parameter in XFCN SETSUBSPACE call!}");
return;
}
bitsToSet = (neighborhoodSize * 2) / SUBSPACE_QUANTUM + 1;
maxTextPtr = GetHandleSize (subspaceHandle) * SUBSPACE_QUANTUM * 8;
getKeyRecord (&prevRec, wordNum - 1, keyFileRefNum);
getKeyRecord (&thisRec, wordNum, keyFileRefNum);
for (instance = prevRec.ccount; instance < thisRec.ccount; ++instance)
{
tp0 = getTextPtr (instance, ptrFileRefNum);
if (tp0 < 0)
{
returnErrorMsg (paramPtr,
"{Sorry, getTextPtr I/O error in XFCN SETSUBSPACE call!}");
return;
}
tp = tp0 - (bitsToSet / 2) * SUBSPACE_QUANTUM;
tpMax = tp + bitsToSet * SUBSPACE_QUANTUM;
if (tp < 0)
tp = 0;
if (tpMax > maxTextPtr)
tpMax = maxTextPtr;
for ( ; tp < tpMax; tp += SUBSPACE_QUANTUM)
setSSBit (tp, setOrClear, subspaceHandle);
}
return;
}
/* function to grab a chunk of text:
*
* ("TEXT", textPtr, textChunkSize, textOffset, textFileRefNum)
* -- returns with (if possible; see below)
* textChunkSize bytes of text from the text file,
* starting at byte number textPtr-textOffset+1 and ending
* just before byte number textPtr-textOffset+textChunkSize+1.
* (The '+1' is to match up with HyperCard's 1-based counting
* convention, rather than the 0-based C convention!!)
* If the file isn't big enough or if textPtr is too near the
* beginning or end of the file, cut off the retrieved text
* at that boundary and insert the words {beginning of dataspace}
* or {end of dataspace}. ***Do no filtering of the text!***
* (Thus, there may be strangenesses if the 'text' file has
* '\0' or other nasty characters in it -- sorry about that!)
* Restrict textChunkSize to <32000 bytes. After the text, on
* a separate line, return three numbers: the byte number of
* the first char returned relative to the beginning of the text
* file, the actual offset within the characters returned
* of the originally-requested textPtr, and the byte number
* of the character after the last char returned relative to
* the beginning of the text file.
*/
void doText (paramPtr)
XCmdBlockPtr paramPtr;
{
int textFileRefNum;
long textPtr, textChunkSize, textOffset, textFileSize, startText,
endText, count;
Handle answer;
char *ansp;
if (paramPtr->paramCount != 5)
{
returnErrorMsg (paramPtr,
"Sorry, wrong number of parameters in XFCN TEXT call!}");
return;
}
textPtr = atol (*(paramPtr->params[1]));
textChunkSize = atol (*(paramPtr->params[2]));
textOffset = atol (*(paramPtr->params[3]));
textFileRefNum = atol (*(paramPtr->params[4]));
GetEOF (textFileRefNum, &textFileSize);
if (textPtr < 0 || textPtr > textFileSize || textOffset < 1 ||
textOffset > textChunkSize || textChunkSize < 1 ||
textChunkSize > 32000 || textFileRefNum == 0)
{
returnErrorMsg (paramPtr,
"{Sorry, bad parameter in XFCN TEXT call!}");
return;
}
startText = textPtr - textOffset + 1;
if (startText < 0)
startText = 0;
endText = textPtr + textChunkSize - textOffset + 1;
if (endText > textFileSize)
endText = textFileSize;
count = endText - startText;
if ((answer = NewHandle (count + 80)) == NULL)
{
returnErrorMsg (paramPtr,
"{Sorry, out of memory error in XFCN TEXT call!}");
return;
}
HLock (answer);
ansp = *answer;
if (startText == 0)
{
strcpy (ansp, "{beginning of dataspace}\r");
ansp += strlen ("{beginning of dataspace}\r");
textOffset = textPtr + strlen ("{beginning of dataspace}\r");
}
if (SetFPos (textFileRefNum, fsFromStart, startText) != noErr ||
FSRead (textFileRefNum, &count, ansp) != noErr)
{
SysBeep (10);
strcpy (ansp,
"{Sorry, file I/O error in XFCN TEXT call!}");
HUnlock (answer);
paramPtr->returnValue = answer;
return;
}
ansp += count;
*ansp++ = '\r';
if (endText == textFileSize)
{
strcpy (ansp, "{end of dataspace}\r");
ansp += strlen ("{end of dataspace}\r");
}
ltoaR (ansp, startText, 12);
ansp += 12;
ltoaR (ansp, textOffset, 12);
ansp += 12;
ltoaR (ansp, endText, 12);
ansp += 12;
*ansp = '\0';
HUnlock (answer);
paramPtr->returnValue = answer;
return;
}
/* ------------------------support routines-------------- */
/* function to set the return value of the XFCN to a chosen error msg;
* if there isn't enough free memory to give us a Handle to the msg,
* beep a bunch and then return!
*/
void returnErrorMsg (paramPtr, msg)
XCmdBlockPtr paramPtr;
char *msg;
{
Handle answer;
int msgLength;
SysBeep (10);
msgLength = strlen (msg);
if ((answer = NewHandle (1 + msgLength)) == NULL)
{
SysBeep (10);
SysBeep (10);
SysBeep (10);
SysBeep (10);
SysBeep (10);
return;
}
strcpy (*answer, msg);
paramPtr->returnValue = answer;
return;
}
/* function to fetch an index key record from the key file; if an
* illegal keyRecNum is asked for, or if any sort of I/O error is
* reported by SetFPos() or FSRead(), return 0 ccount and blank kkey....
*/
void getKeyRecord (keyRecp, keyRecNum, keyFileRefNum)
KEY_RECORD *keyRecp;
long keyRecNum;
int keyFileRefNum;
{
long count;
register int i;
count = sizeof(KEY_RECORD);
if (keyRecNum < 0 ||
SetFPos (keyFileRefNum, fsFromStart,
keyRecNum * sizeof(KEY_RECORD)) != noErr ||
FSRead (keyFileRefNum, &count, keyRecp) != noErr)
{
for (i = 0; i < KEY_LENGTH; ++i)
keyRecp->kkey[i] = ' ';
keyRecp->ccount = 0;
}
return;
}
/* function to fetch the value of the nth ptr from file ptrFileRefNum;
* return illegal value (-1) for result if something goes wrong....
*/
long getTextPtr (n, ptrFileRefNum)
long n;
int ptrFileRefNum;
{
long bytes = sizeof(long), result;
if (SetFPos (ptrFileRefNum, fsFromStart, n * sizeof(long)) != noErr ||
FSRead (ptrFileRefNum, &bytes, &result) != noErr)
return (-1);
return (result);
}
/* function to fetch a filtered line of text from the file ... fill
* in with blanks if try to fetch from before the beginning of the
* file or after the end of the file ... filter all control characters
* by turning them into spaces ....
*/
void getContextLine (bytes, start, refNum, ans)
int refNum;
long bytes, start;
char *ans;
{
register int i = 0;
long origbytes;
origbytes = bytes;
if (start < 0)
for (i = 0; i < -start; ++i)
ans[i] = ' ';
bytes -= i;
if (SetFPos (refNum, fsFromStart, start + i) != noErr ||
FSRead (refNum, &bytes, ans + i) != noErr)
{
if (origbytes >
strlen ("{Sorry, file I/O error in XFCN CONTEXT call!}"))
{
strcpy (ans, "{Sorry, file I/O error in XFCN CONTEXT call!}");
bytes = origbytes;
}
else
SysBeep (10);
}
if (bytes + i < origbytes)
for (i += bytes; i < origbytes; ++i)
ans[i] = ' ';
for (i = 0; i < origbytes; ++i)
if (ans[i] < 32 || ans[i] == 127)
ans[i] = ' ';
return;
}
/* function to format an index record, with the count right-justified
* followed by a space, then the key word itself, followed by a '\r'.
*/
void buildIndexAnswer (ansp, cwidth, kwidth, count, key)
char *ansp, *key;
long count;
int cwidth, kwidth;
{
register int i;
ltoaR (ansp, count, cwidth);
ansp += cwidth;
*ansp++ = ' ';
for (i = 0; i < kwidth; ++i)
*ansp++ = *key++;
*ansp = '\r';
return;
}
/* function to format an index record when working in a subspace; like
* buildIndexAnswer function above, but with information about how many
* instances of each word are in the working subspace. Specifically,
* give a percentage estimate based on the last maxIndexSampleCount
* instances for a word that occurs more than maxIndexSampleCount times
* (e.g., " ~37% "), and for less frequently occurring words give the
* actual fraction of valid/total instances (e.g., " 17/49 ").
*/
void buildSubIndexAnswer (ansp, cwidth, kwidth, maxSample,
prevCcount, thisCcount, key, subspaceHandle, ptrFileRefNum)
char *ansp, *key;
int cwidth, kwidth, ptrFileRefNum;
long maxSample, prevCcount, thisCcount;
Handle subspaceHandle;
{
long startCcount, instance, goodInstances;
register int i;
int goodPercent, subWidth;
if (thisCcount - prevCcount > maxSample)
startCcount = thisCcount - maxSample;
else
startCcount = prevCcount;
goodInstances = 0;
for (instance = startCcount; instance < thisCcount; ++instance)
if (inSubspace (getTextPtr (instance, ptrFileRefNum),
subspaceHandle))
++goodInstances;
if (thisCcount - prevCcount > maxSample)
{
goodPercent = (100 * goodInstances) / (thisCcount - startCcount);
*ansp++ = '~';
ltoaR (ansp, goodPercent, 3);
ansp += 3;
*ansp++ = '%';
for (i = 5; i < cwidth; ++i)
*ansp++ = ' ';
}
else
{
subWidth = (cwidth - 1) / 2;
ltoaR (ansp, goodInstances, subWidth);
ansp += subWidth;
*ansp++ = '/';
ltoaR (ansp, thisCcount - prevCcount, subWidth);
ansp += subWidth;
for (i = 2 * subWidth + 1; i < cwidth; ++i)
*ansp++ = ' ';
}
*ansp++ = ' ';
for (i = 0; i < kwidth; ++i)
*ansp++ = *key++;
*ansp = '\r';
return;
}
/* function to determine if a given textPtr is in the subspace of
* interest ... do it by a simple computation and look-up in the
* bit array of subspace flags...
*/
int inSubspace (textPtr, subspaceHandle)
long textPtr;
Handle subspaceHandle;
{
int bitNum;
long byteNum;
bitNum = (textPtr % (8 * SUBSPACE_QUANTUM)) / SUBSPACE_QUANTUM;
byteNum = textPtr / (8 * SUBSPACE_QUANTUM);
return (((*subspaceHandle)[byteNum] >> bitNum) & 1);
}
/* function to set or clear a bit in the subspace array...
*/
void setSSBit (textPtr, setOrClear, subspaceHandle)
long textPtr;
int setOrClear;
Handle subspaceHandle;
{
int bitNum;
long byteNum;
bitNum = (textPtr % (8 * SUBSPACE_QUANTUM)) / SUBSPACE_QUANTUM;
byteNum = textPtr / (8 * SUBSPACE_QUANTUM);
if (setOrClear)
*(*subspaceHandle + byteNum) |= 1 << bitNum;
else
*(*subspaceHandle + byteNum) &= ~(1 << bitNum);
return;
}
/* function to copy a string from one place to another, in a rather
* obvious fashion ... adapted from the LSC library, and K&R p.101 ....
*/
char *strcpy (s1, s2)
register char *s1, *s2;
{
char *s = s1;
while (*s1++ = *s2++)
;
return (s);
}
/* function to determine the length of a string ... standard thing,
* adapted from the LSC library, and K&R p.98 ....
*/
int strlen (s)
register char *s;
{
char *s0 = s;
while (*s++)
;
return (s - s0 - 1);
}
/* my function to compare two strings and give a result as to who is
* alphabetically earlier. Note that this is almost the same as strncmp()
* with the fixed value of KEY_LENGTH as the maximum comparison distance,
* except that I must be sure to handle the non-ASCII funny letters in
* the Apple character set properly/consistently ... hence the need to
* declare s1 and s2 to be type unsigned char *...
*/
int zstrcmp (s1, s2)
register unsigned char *s1, *s2;
{
register int n = KEY_LENGTH;
for (; --n && *s1 == *s2; s1++, s2++)
if (!*s1)
break;
return (*s1 - *s2);
}
/* function to convert alphanumeric string to a long int, from K&R & LSC
* library.... simplified to avoid using isspace() & isdigit() ....
*/
long atol (s)
register char *s;
{
int signflag = 0;
register long r = 0;
while (*s == ' ')
s++;
if (*s == '-')
{
signflag = 1;
s++;
}
else if (*s == '+')
s++;
while (*s >= '0' && *s <= '9')
r = r * 10 + (*s++ - '0');
return (signflag ? -r : r);
}
/* function to convert a number into a string of width maxDigits and
* store it right-justified, blank-filled on left; based on K&R p. 60
* example of itoa().
*
* Error handling: put a '>' or '<' in leading place to warn of an
* overflow (no room for digits on a positive or negative number,
* respectively), and put a '^' in leading place to warn if no room
* for '-' sign on negative number...
*/
void ltoaR (ansp, n, maxDigits)
register char *ansp;
register long n;
int maxDigits;
{
register int i;
long sign;
i = maxDigits - 1;
if ((sign = n) < 0)
n = -n;
do
{
ansp[i--] = n % 10 + '0';
}
while ((n /= 10) > 0 && i >= 0);
if (i < 0 && n > 0) /* ran out of room with digits still to go */
{
if (sign > 0)
ansp[0] = '>'; /* positive overflow signal */
else
ansp[0] = '<'; /* negative overflow signal */
}
else
{
if (sign < 0)
if (i >= 0)
ansp[i--] = '-';
else
ansp[0] = '^'; /* no room for '-' sign signal */
for ( ; i >= 0; --i)
ansp[i] = ' ';
}
return;
}
/* -----------------command syntax & calling conventions--------------- */
/*
* ("CONTEXT", instanceNum, contextLines, targetContextLine,
* contextLineLength, contextWordOffset, maxContextLinesSkipped,
* ptrFileRefNum, textFileRefNum, subspaceHandle)
* -- returns with contextLines of display followed by contextLines
* of instanceNum-textPtr pairs, with context instance instanceNum
* on line targetContextLine...
*
* ("EMPTYSUBSPACE", subspaceHandle)
* -- returns quietly with nothing if it successfully sets all bits
* in the subspace flag array to zero; beeps and gives an error msg
* if it fails somehow...
*
* ("FILLSUBSPACE", subspaceHandle)
* -- returns quietly with nothing if it successfully sets all bits
* in the subspace flag array to one; beeps and gives an error msg
* if failure...
*
* ("INDEX", wordNum, indexLines, maxIndexSampleCount, indexCountWidth,
* indexKeyWidth, keyFileRefNum, ptrFileRefNum, subspaceHandle)
* -- returns with indexLines of index window display, followed by
* indexLines of instanceNums. The index lines are:
* indexCountWidth columns of occurrence count info (right-justified),
* a blank column, and indexKeyWidth columns of keyWord (in all
* caps, left-justified). Demand that indexCountWidth be at least
* 5, to allow for subindex count display, and that indexKeyWidth
* be in the range 1 through KEY_LENGTH = 28 ...
*
* ("LOCATE", targetString, keyFileRefNum)
* -- returns wordNum for the targetString if it is found in the
* key file; otherwise returns wordNum for the word alphabetically
* preceding targetString followed by "{targetString not found!}"
* on the second line of the answer...
*
* ("NEWSUBSPACE", textFileRefNum)
* -- returns subspaceHandle for a new subspace that it creates, big
* enough to do subspace browsing -- but does NOT initialize that
* subspace or check to see whether another subspace already
* exists. Beeps and gives error msg if it fails...
*
* ("RELEASESUBSPACE", subspaceHandle)
* -- returns quietly with nothing if successful in releasing the
* subspaceHandle, or noisily with an error message if it fails...
*
* ("SETSUBSPACEBITS", wordNum, neighborhoodSize, setOrClear,
* keyFileRefNum, ptrFileRefNum, subspaceHandle)
* -- returns quietly with nothing if it is successful in setting or
* clearing (depending on setOrClear's value, 0 or non-0) the
* bits in the subspace flag array in the neighborhood of the
* chosen word(s); gives an error msg if there was a problem.
* neighborhoodSize is in characters and is used to determine
* how many bits to set/clear on each side of the instances...
*
* ("TEXT", textPtr, textChunkSize, textOffset, textFileRefNum)
* -- returns with (if possible; see below)
* textChunkSize bytes of text from the text file,
* starting at byte number textPtr-textOffset+1 and ending
* just before byte number textPtr-textOffset+textChunkSize+1.
* (The '+1' is to match up with HyperCard's 1-based counting
* convention, rather than the 0-based C convention!!)
* If the file isn't big enough or if textPtr is too near the
* beginning or end of the file, cut off the retrieved text
* at that boundary and insert the words {beginning of dataspace}
* or {end of dataspace}. ***Do no filtering of the text!***
* (Thus, there may be strangenesses if the 'text' file has
* '\0' or other nasty characters in it -- sorry about that!)
* Restrict textChunkSize to <32000 bytes. After the text, on
* a separate line, return three numbers: the byte number of
* the first char returned relative to the beginning of the text
* file, the actual offset within the characters returned
* of the originally-requested textPtr, and the byte number
* of the character after the last char returned relative to
* the beginning of the text file.
*/